package es.us.idinfor.rmplugin.discretizer;

import java.util.Collections;
import java.util.List;
import java.util.Map;

import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Lists;
import com.google.common.collect.Table;
import com.rapidminer.example.Attribute;
import com.rapidminer.example.Attributes;
import com.rapidminer.example.Example;
import com.rapidminer.example.ExampleSet;
import com.rapidminer.example.SimpleAttributes;

import es.us.idinfor.rmplugin.algorithm.Range;
import es.us.idinfor.rmplugin.statistic.Metrics;

public abstract class AbstractDiscretizer<T extends Range> {
	private ExampleSet es;
	
	/**
	 * Creates a discretizer with an example set.
	 * 
	 * @param exampleSet The example set.
	 */
	public AbstractDiscretizer(ExampleSet exampleSet) {
		es = exampleSet;
	}
	
	/**
	 * Gets the example set.
	 * 
	 * @return The example set.
	 */
	public ExampleSet getExampleSet() {
		return es;
	}
	
	/**
	 * Sets the new example set.
	 * 
	 * @param exampleSet The example set.
	 */
	public void setExampleSet(ExampleSet exampleSet) {
		es = exampleSet;
	}
	
	/**
	 * This method discretize the values of the attribute.
	 * 
	 * @param examples The set of examples.
	 * @param attribute The attribute that will be discretized.
	 */
	public void discretize(Attribute attribute) {
		Attributes attributes = new SimpleAttributes();
		attributes.addRegular(attribute);
		
		discretize(attributes, false, 0);
	}
	
	/**
	 * This method discretize the values of the attribute.
	 * 
	 * @param attributes The attributes that will be discretized.
	 * @param average <code>true</code> if the labels must be the mean values.
	 * <code>false</code> in other case.
	 * @param outlierPercentage The percentage of values that must be considered outlier.
	 */
	public void discretize(Attributes attributes, boolean average, double outlierPercentage) {
		if(es.size() > 0) {
			int numClasses = Metrics.getNumberOfDifferentValues(es, es.getAttributes().getLabel());
			Map<Attribute, T> ranges = computeValues(attributes, numClasses);
			Table<Attribute, Double, Double> table = HashBasedTable.create();
			double[] attRanges = null;
			double oldValue = Double.NaN;
			
			if(average) {
				table = averageValuesRanges(ranges, outlierPercentage);
			}
			else {
				for(Attribute att: attributes) {
					if(att.isNumerical()) {
						attRanges = ranges.get(att).getRanges();
						
						for(int i = 0; i < attRanges.length; i++) {
							table.put(att, attRanges[i], new Double(i));
						}
					}
				}
			}
			
			for(Example ex: es) {
				for(Attribute att: attributes) {
					if(att.isNumerical()) {
						attRanges = ranges.get(att).getRanges();
						oldValue = ex.getValue(att);
						
						for(double rangeValue: attRanges) {
							if(oldValue <=rangeValue) {
								ex.setValue(att, table.get(att, rangeValue));
								break;
							}
						}
					}
				}
			}
		}
	}
	
	/**
	 * This method gets the arithmetic average values from a lists of values for all attributes
	 * of a data set and their ranges of values.
	 * 
	 * @param examples The set of examples.
	 * @param ranges The ranges of values.
	 * @param outlierPercentage The percentage of values that must be considered outlier.
	 * @return A table with the arithmetic average relation between attributes and ranges.
	 */
	private Table<Attribute, Double, Double> averageValuesRanges(Map<Attribute, T> ranges, double outlierPercentage) {
		Table<Attribute, Double, Double> res = HashBasedTable.create();
		List<Double> list = null;
		List<Double> auxList = null;
		double[] attRanges = null;
		double aux = Double.NaN;
		
		for(Attribute att: es.getAttributes()) {
			list = Lists.newArrayList();
			
			for(Example ex: es) {
				list.add(ex.getValue(att));
			}
			
			Collections.sort(list);
			
			attRanges = ranges.get(att).getRanges();
			
			for(int i = 0; i < attRanges.length; i++) {
				if(attRanges[i] == Double.POSITIVE_INFINITY) {
					int discardedValues = new Double((outlierPercentage / 100) * list.size()).intValue();
					auxList = Lists.newArrayList(list.subList(0, list.size() - discardedValues));
					
					list.clear();
				}
				else {
					auxList = Lists.newArrayList(list.subList(0, list.lastIndexOf(attRanges[i]) + 1));
					
					if(i == 0) {
						int discardedValues = new Double((outlierPercentage / 100) * auxList.size()).intValue();
						auxList = auxList.subList(discardedValues, auxList.size());
					}
					
					list = list.subList(list.lastIndexOf(attRanges[i]) + 1, list.size());
				}
				
				aux = Metrics.getMetric(auxList, Metrics.ARITHMETIC_AVERAGE);
				
				res.put(att, attRanges[i], aux);
			}
		}
		
		return res;
	}
	
	/**
	 * Returns the number of classes with examples.
	 * 
	 * @param list The list of the example set values.
	 * @return The number of classes.
	 */
	public int getNumberOfClassesWithExamples(List<CumDiscretizerBlock> list) {
		CumDiscretizerBlock cdb = list.get(list.size() - 1);
		int[] accumulated = cdb.getAccumulated();
		int res = 0;
		
		for(int i = 0; i < accumulated.length; i++) {
			if(accumulated[i] != 0) {
				res++;
			}
		}
		
		return res;
	}
	
	/**
	 * This method computes the values of examples.
	 * 
	 * @param attributes The attributes.
	 * @param numClasses The number of classes.
	 * @return The ranges for each attribute.
	 */
	public abstract Map<Attribute, T> computeValues(Attributes attributes, int numClasses);
}
